package com.ejiaoyi.common.util;

import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * 压缩工具类
 *
 * @author unknownChivalrous
 * @since 2020-5-11
 */
@Slf4j
public class CompressUtil {

    /**
     * zip 非加密压缩 使用默认压缩方式 和 压缩级别
     *
     * @param inPath    需要压缩的文件或者文件夹
     * @param outPath   输出的压缩文件路径
     * @param createDir inPath为文件夹是 压缩是否创建文件目录结构
     * @return 是否压缩成功
     */
    public static boolean zip(String inPath, String outPath, boolean createDir) {
        return CompressUtil.zip(inPath, outPath, createDir, null, CompressionMethod.DEFLATE, CompressionLevel.NORMAL, null, null);
    }

    /**
     * zip 非加密压缩
     *
     * @param inPath            需要压缩的文件或者文件夹
     * @param outPath           输出的压缩文件路径
     * @param createDir         inPath为文件夹是 压缩是否创建文件目录结构
     * @param compressionMethod 压缩方式
     * @param compressionLevel  压缩级别
     * @return 是否压缩成功
     */
    public static boolean zip(String inPath, String outPath, boolean createDir,
                              CompressionMethod compressionMethod, CompressionLevel compressionLevel) {
        return CompressUtil.zip(inPath, outPath, createDir, null, compressionMethod, compressionLevel, null, null);
    }

    /**
     * zip 加密压缩 加密方式 ZIP_STANDARD 使用默认压缩方式 和 压缩级别
     *
     * @param inPath    需要压缩的文件或者文件夹
     * @param outPath   输出的压缩文件路径
     * @param createDir inPath为文件夹是 压缩是否创建文件目录结构
     * @param password  密码
     * @return 是否压缩成功
     */
    public static boolean zipEncryptStandard(String inPath, String outPath, boolean createDir, String password) {
        return CompressUtil.zip(inPath, outPath, createDir, password, CompressionMethod.DEFLATE, CompressionLevel.NORMAL, EncryptionMethod.ZIP_STANDARD, null);
    }

    /**
     * zip 加密压缩 加密方式 ZIP_STANDARD
     *
     * @param inPath            需要压缩的文件或者文件夹
     * @param outPath           输出的压缩文件路径
     * @param createDir         inPath为文件夹是 压缩是否创建文件目录结构
     * @param password          密码
     * @param compressionMethod 压缩方式
     * @param compressionLevel  压缩级别
     * @return 是否压缩成功
     */
    public static boolean zipEncryptStandard(String inPath, String outPath, boolean createDir, String password,
                                             CompressionMethod compressionMethod, CompressionLevel compressionLevel) {
        return CompressUtil.zip(inPath, outPath, createDir, password, compressionMethod, compressionLevel, EncryptionMethod.ZIP_STANDARD, null);
    }

    /**
     * zip 加密压缩 加密方式 ZIP_STANDARD_VARIANT_STRONG 使用默认压缩方式 和 压缩级别
     *
     * @param inPath    需要压缩的文件或者文件夹
     * @param outPath   输出的压缩文件路径
     * @param createDir inPath为文件夹是 压缩是否创建文件目录结构
     * @param password  密码
     * @return 是否压缩成功
     */
    public static boolean zipEncryptStandardStrong(String inPath, String outPath, boolean createDir, String password) {
        return CompressUtil.zip(inPath, outPath, createDir, password, CompressionMethod.DEFLATE, CompressionLevel.NORMAL, EncryptionMethod.ZIP_STANDARD_VARIANT_STRONG, null);
    }

    /**
     * zip 加密压缩 加密方式 ZIP_STANDARD_VARIANT_STRONG
     *
     * @param inPath            需要压缩的文件或者文件夹
     * @param outPath           输出的压缩文件路径
     * @param createDir         inPath为文件夹是 压缩是否创建文件目录结构
     * @param password          密码
     * @param compressionMethod 压缩方式
     * @param compressionLevel  压缩级别
     * @return 是否压缩成功
     */
    public static boolean zipEncryptStandardStrong(String inPath, String outPath, boolean createDir, String password,
                                                   CompressionMethod compressionMethod, CompressionLevel compressionLevel) {
        return CompressUtil.zip(inPath, outPath, createDir, password, compressionMethod, compressionLevel, EncryptionMethod.ZIP_STANDARD_VARIANT_STRONG, null);
    }

    /**
     * zip 加密压缩 加密方式 AES_128
     *
     * @param inPath    需要压缩的文件或者文件夹
     * @param outPath   输出的压缩文件路径
     * @param createDir inPath为文件夹是 压缩是否创建文件目录结构
     * @param password  密码
     * @return 是否压缩成功
     */
    public static boolean zipEncryptAES128(String inPath, String outPath, boolean createDir, String password) {
        return CompressUtil.zip(inPath, outPath, createDir, password, CompressionMethod.DEFLATE, CompressionLevel.NORMAL, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_128);
    }

    /**
     * zip 加密压缩 加密方式 AES_128
     *
     * @param inPath            需要压缩的文件或者文件夹
     * @param outPath           输出的压缩文件路径
     * @param createDir         inPath为文件夹是 压缩是否创建文件目录结构
     * @param password          密码
     * @param compressionMethod 压缩方式
     * @param compressionLevel  压缩级别
     * @return 是否压缩成功
     */
    public static boolean zipEncryptAES128(String inPath, String outPath, boolean createDir, String password,
                                           CompressionMethod compressionMethod, CompressionLevel compressionLevel) {
        return CompressUtil.zip(inPath, outPath, createDir, password, compressionMethod, compressionLevel, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_128);
    }

    /**
     * zip 加密压缩 加密方式 AES_256
     *
     * @param inPath    需要压缩的文件或者文件夹
     * @param outPath   输出的压缩文件路径
     * @param createDir inPath为文件夹是 压缩是否创建文件目录结构
     * @param password  密码
     * @return 是否压缩成功
     */
    public static boolean zipEncryptAES256(String inPath, String outPath, boolean createDir, String password) {
        return CompressUtil.zip(inPath, outPath, createDir, password, CompressionMethod.DEFLATE, CompressionLevel.NORMAL, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256);
    }

    /**
     * zip 加密压缩 加密方式 AES_256
     *
     * @param inPath            需要压缩的文件或者文件夹
     * @param outPath           输出的压缩文件路径
     * @param createDir         inPath为文件夹是 压缩是否创建文件目录结构
     * @param password          密码
     * @param compressionMethod 压缩方式
     * @param compressionLevel  压缩级别
     * @return 是否压缩成功
     */
    public static boolean zipEncryptAES256(String inPath, String outPath, boolean createDir, String password,
                                           CompressionMethod compressionMethod, CompressionLevel compressionLevel) {
        return CompressUtil.zip(inPath, outPath, createDir, password, compressionMethod, compressionLevel, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256);
    }

    /**
     * zip 压缩
     *
     * @param inPath            需要压缩的文件或者文件夹
     * @param outPath           输出的压缩文件路径
     * @param createDir         inPath为文件夹是 压缩是否创建文件目录结构
     * @param password          密码
     * @param compressionMethod 压缩方式
     * @param compressionLevel  压缩级别
     * @param encryptionMethod  加密方式 password not empty时有效
     * @param aesKeyStrength    aes key 的类型 当 encryptionMethod 为 AES时有效
     * @return 是否压缩成功
     */
    private static boolean zip(String inPath, String outPath, boolean createDir, String password,
                               CompressionMethod compressionMethod, CompressionLevel compressionLevel,
                               EncryptionMethod encryptionMethod, AesKeyStrength aesKeyStrength) {
        boolean encrypt = StringUtils.isNotEmpty(password);
        if (encrypt) {
            Assert.notNull(encryptionMethod, "param encryptionMethod can not be null");
        }

        try {
            File inFile = new File(inPath);
            FileUtil.createFile(outPath);

            ZipParameters zipParameters = new ZipParameters();
            zipParameters.setCompressionMethod(compressionMethod);
            zipParameters.setCompressionLevel(compressionLevel);

            if (encrypt) {
                zipParameters.setEncryptFiles(true);
                zipParameters.setEncryptionMethod(encryptionMethod);
                if (encryptionMethod == EncryptionMethod.AES) {
                    zipParameters.setAesKeyStrength(aesKeyStrength);
                }
            }

            ZipFile zip;

            if (encrypt) {
                zip = new ZipFile(inFile, password.toCharArray());
            } else {
                zip = new ZipFile(inFile);
            }

            if (inFile.isDirectory()) {
                if (!createDir) {
                    File[] files = inFile.listFiles();
                    if (ArrayUtils.isNotEmpty(files)) {
                        List<File> temp = new ArrayList<>(files.length);
                        CollectionUtils.addAll(temp, files);
                        zip.addFiles(temp, zipParameters);
                    }
                } else {
                    zip.addFolder(inFile, zipParameters);
                }
            } else {
                zip.addFile(inFile, zipParameters);
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error("zip error : " + e.getMessage());
            return false;
        }

        return true;
    }

    /**
     * 解压缩 zip 无密码
     *
     * @param zipPath zip文件路径
     * @param outDir  解压路径
     * @return 是否解压成功
     * @throws ZipException zip压缩异常
     */
    public static boolean unzip(String zipPath, String outDir) throws ZipException {
        return CompressUtil.unzip(zipPath, outDir, null);
    }

    /**
     * 解压缩 zip 无密码
     *
     * @param zipFile zip文件
     * @param outDir  解压路径
     * @return 是否解压成功
     * @throws ZipException zip压缩异常
     */
    public static boolean unzip(File zipFile, String outDir) throws ZipException {
        return CompressUtil.unzip(zipFile, outDir, null);
    }

    /**
     * 解压缩 zip 带有密码
     *
     * @param zipPath  zip文件路径
     * @param outDir   解压路径
     * @param password 密码
     * @return 是否解压成功
     * @throws ZipException zip压缩异常
     */
    public static boolean unzip(String zipPath, String outDir, String password) throws ZipException {
        return CompressUtil.unzip(new File(zipPath), outDir, password);
    }

    /**
     * 解压缩 zip 带有密码
     *
     * @param zipFile  zip文件
     * @param outDir   解压路径
     * @param password 密码
     * @return 是否解压成功
     * @throws ZipException zip压缩异常
     */
    public static boolean unzip(File zipFile, String outDir, String password) throws ZipException {
        ZipFile zip = new ZipFile(zipFile);
        zip.setCharset(StandardCharsets.UTF_8);

        if (!zip.isValidZipFile()) {
            throw new ZipException("unzip file valid zip file failed");
        }

        File outFile = new File(outDir);
        if (outFile.isDirectory() && !outFile.exists()) {
            outFile.mkdirs();
        }

        if (zip.isEncrypted()) {
            zip.setPassword(password.toCharArray());
        }
        zip.extractAll(outDir);

        return true;
    }
}
